/*
Hermes Core: General Purpose Mobile Agent Platform
Copyright (C) 2006 UNICAM - Università degli Studi di Camerino

Group leaders: Flavio Corradini and Emanuela Merelli

Developers: Ezio Bartocci, Diego Bonura, Rosario Culmone,
Davide Lorenzoli, Leonardo Mariani, Francesca Piersigilli, 
Lorenzo Scortichini, Leonardo Vito.

GNU Lesser Public License

This library is free software; you can redistribute it 
and/or modify it under the terms of the GNU Lesser General 
Public License as published by the Free Software Foundation, 
version 2.1 of the License.

This library is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of 
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
See the GNU Library General Public License for more details.

You should have received a copy of the GNU Library General Public 
License along with this library; if not, write to the
Free Software Foundation, Inc., 59 Temple Place - Suite 330,
Boston, MA 02111-1307, USA.

Emanuela Merelli
Dipartimento di Matematica ed Informatica
Università di Camerino
via Madonna delle Carceri, 62032 Camerino, Italy
E.Mail: emanuela.merelli@unicam.it
*/
package hermesV2.basicServices.communication;

import hermesV2.CurrentAgentLocation;
import hermesV2.Envelope;
import hermesV2.Message;
import hermesV2.PlaceAddress;

import hermesV2.agent.Identificator;

import hermesV2.core.id.Trace;

import hermesV2.core.net.EnvelopeIO;

import hermesV2.util.HermesLogger;
import hermesV2.util.VectorQueue;

import java.io.IOException;

import java.util.Hashtable;

/**
 * The CommunicationManager represents the component that implements the high
 * level communication between user and service agents. It permits to receive
 * and to send messages between local and remote agents using the net component
 * of the core layer. The trace of the right destination place address of every
 * user agent is done automatically.
 * 
 * @author Diego Bonura
 */
public class CommunicationManager implements Receive, Send, Runnable {
	/** It's a flag to manage the stop of the communication component */
	private boolean interrupted;

	/** This is the interface of the net component */
	private EnvelopeIO envelopeIO;

	/** This is the interface of the id component */
	private Trace trace;

	/** This is the place address of the running place */
	private PlaceAddress currentPlaceAddress;

	/** It contains blocking queue associated to every agent in the place */
	private Hashtable<String, VectorQueue<Message <?>>>  receptionHashtable;

	private LazyMessageHandle lazyMessageHandle;

	/**
	 * Creates a new CommunicationManager object.
	 * 
	 * @param currentPlaceAddress
	 *            The current place address.
	 */
	public  CommunicationManager(PlaceAddress currentPlaceAddress) {
		interrupted = false;
		this.currentPlaceAddress = currentPlaceAddress;
		receptionHashtable = new Hashtable<String, VectorQueue<Message <?>>> ();
		lazyMessageHandle = new LazyMessageHandle(this, currentPlaceAddress);
		lazyMessageHandle.start();
	}

	/**
	 * It sets to the communication component the net interface of the core
	 * layer.
	 * 
	 * @param envelopeIO
	 *            The interface of the net component that permits the to
	 *            communicate with other places.
	 */
	public void setEnvelopeIO(EnvelopeIO envelopeIO) {
		this.envelopeIO = envelopeIO;
	}

	/**
	 * It sets to the communication component the id interface of the core
	 * layer.
	 * 
	 * @param trace
	 *            The interface of the id component.
	 */
	public void setTrace(Trace trace) {
		this.trace = trace;
	}

	/**
	 * It starts the thread of the communication component. First it waits new
	 * message from agents and then it creates a new InputMessagesRequest thread
	 * to handle it.
	 */
	public void run() {
		if (envelopeIO == null) {
			HermesLogger.log(HermesLogger.WARNING, "The net component null!");
		} else {
			//ThreadPool tp = new ThreadPool(1);
			while (!interrupted) {
				InputMessageRequest imr = new InputMessageRequest(envelopeIO
						.receiveEnvelopeBlocking(), receptionHashtable,
						lazyMessageHandle.getLazyMessageHandleId());

				//tp.assign(imr);
				imr.start();
			}
		}
	}

	/**
	 * Sends a message to a UserAgent, it tries to locate the agent and after
	 * sends the message locally or remottelly using the net component of the
	 * core layer.
	 * @param <T>
	 * 
	 * @param message
	 *            The message to send.
	 * 
	 * @throws SendFailedException
	 *             When a message could not be sent to a destination.
	 */
	public <T> void sendMessageToUserAgent(Message<T> message)
			throws SendFailedException {
		Identificator receiverAgentId = message.getReceiverAgentId();

		if (trace == null) {
			throw new SendFailedException(
					"CommunicationManager: No Id component found");
		}
		PlaceAddress currentAgentPlaceAddress = trace
				.getCurrentAddress(receiverAgentId);
		if (currentAgentPlaceAddress == null) {
			//i have to search the position remotelly
			Envelope<?> reply = null;

			try {
				reply = envelopeIO.sendEnvelope(new Envelope<Identificator>(
						Envelope.ADDRESS_REQUEST, receiverAgentId,
						currentPlaceAddress, receiverAgentId
								.getBornPlaceAddress()));
			} catch (IOException ioe) {
				throw new SendFailedException(ioe.getMessage());
			}

			if (reply.getType() == Envelope.ADDRESS_ENTER) {
				try {
					//					i'm going to check the correctness of agent id
					if (((CurrentAgentLocation) reply.getObject())
							.getIdentificator().equals(receiverAgentId)) {
						//Sends the real message to the current agent
						// location found.
						Envelope<?> env = envelopeIO.sendEnvelope(new Envelope<Message<?>>(
								Envelope.MESSAGE_TO_USERAGENT, message,
								currentPlaceAddress,
								((CurrentAgentLocation) reply.getObject())
										.getPlaceAddress()));

						if (env.getType() != Envelope.RECEPTION_OK) {
							throw new SendFailedException(env.getObject() + "");
						}
						HermesLogger.log(HermesLogger.DEBUG,
								"Message sent remotelly to place: "
										+ ((CurrentAgentLocation) reply
												.getObject()).getPlaceAddress()
										+ " Message: " + message);

					} else {
						throw new SendFailedException(
								"CommunicationManager: The agent location or identificator malformed!!!");
					}
				} catch (ClassNotFoundException cnfe) {
					HermesLogger.log(HermesLogger.WARNING,
							"ClassNotFoundException", cnfe);
				} catch (IOException ioe) {
					throw new SendFailedException(ioe.getMessage());
				}
			} else {
				throw new SendFailedException(
						"CommunicationManager: Impossible to locate the user agent "+message.getReceiverAgentId());
			}
		} else {
			if (currentAgentPlaceAddress.equals(currentPlaceAddress)) {
				//Sends the real message to the current place without
				// extern connection.
				Envelope<?> envelope = new Envelope<Message<?>>(Envelope.MESSAGE_TO_USERAGENT,
						message, currentPlaceAddress, currentAgentPlaceAddress);
				Thread imr = new InputMessageRequest(envelope,
						receptionHashtable, lazyMessageHandle
								.getLazyMessageHandleId());
				imr.start();
				try {
					imr.join();
				} catch (InterruptedException ie) {
					ie.printStackTrace();
				}
			} else {
				try {
					Envelope<?> env = envelopeIO.sendEnvelope(new Envelope<Message<?>>(
							Envelope.MESSAGE_TO_USERAGENT, message,
							currentPlaceAddress, currentAgentPlaceAddress));

					if (env.getType() != Envelope.RECEPTION_OK) {
						throw new SendFailedException(env.getObject() + "");
					}
					HermesLogger.log(HermesLogger.DEBUG,
							"Message sent remotelly to place: "
									+ currentAgentPlaceAddress + " Message: "
									+ message);

				} catch (ClassNotFoundException cnfe) {
					HermesLogger.log(HermesLogger.WARNING,
							"ClassNotFoundException", cnfe);
				} catch (IOException ioe) {
					throw new SendFailedException(ioe.getMessage());
				}
			}
		}
	}

	/**
	 * Sends a message to a ServiceAgent, it tries to locate the agent locally
	 * and after sends the message locally to the service agent. No remote
	 * messages to servie agents are allowed.
	 * @param <T>
	 * @param <T>
	 * 
	 * @param message
	 *            The message to send.
	 * 
	 * @throws SendFailedException
	 *             When a message could not be sent to a destination.
	 */
	public <T> void sendMessageToServiceAgent(Message<T> message)
			throws SendFailedException {
		Identificator receiverAgentId = message.getReceiverAgentId();

		if (trace == null) {
			throw new SendFailedException(
					"CommunicationManager: No Id component found");
		}
		PlaceAddress currentAgentPlaceAddress = trace
				.getCurrentAddress(receiverAgentId);

		if (currentAgentPlaceAddress != null) {
			//				Sends the real message to the current place without extern
			// connection.
			Envelope<?> envelope = new Envelope<Message<?>>(Envelope.MESSAGE_TO_SERVICEAGENT,
					message, currentPlaceAddress, currentAgentPlaceAddress);
			Thread imr = new InputMessageRequest(envelope, receptionHashtable,
					lazyMessageHandle.getLazyMessageHandleId());
			imr.start();
		} else {
			throw new SendFailedException(
					"CommunicationManager: Impossible to communicate with the service agent");

		}
	}

	/**
	 * Every agent wants to receive message must register it self into the
	 * communication component. This method permits to register an agent to the
	 * component and it creates the blocking queue associated to the right agent
	 * identificator. After the registration new messages could be stored into
	 * the queue.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive messages.
	 */
	public synchronized void setAgentReception(Identificator agentId) {
		if (receptionHashtable.containsKey(agentId.toString()) == false) {
		    MessageQueue<Message <?>> bq = new MessageQueue<Message <?>> ();
			receptionHashtable.put(agentId.toString(), bq);
			HermesLogger.log(HermesLogger.DEBUG, "Agent:" + agentId
					+ " now registered!");

		} else {
			HermesLogger.log(HermesLogger.DEBUG, "Agent:" + agentId
					+ " already registered...nothing changed!");
		}
	}

	/**
	 * Every agent wants to receive message must register it self into the
	 * communication component. This method permits to register an agent to the
	 * component and to restore old message queue associated to the right agent
	 * identificator. After the registration new messages could be stored into
	 * the old queue.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive messages.
	 * @param vq
	 *            The old message queue agent.
	 */
	public synchronized void setAgentReception(Identificator agentId, MessageQueue<Message <?>>  vq) {
		if (vq == null) {
			setAgentReception(agentId);
		} else if (receptionHashtable.containsKey(agentId.toString()) == false) {
			receptionHashtable.put(agentId.toString(), vq);
			HermesLogger.log(HermesLogger.DEBUG, "Agent:" + agentId
					+ " now registered and restored vector queue!");
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " already registered...nothing changed or message queue cast exception");
		}
	}

	/**
	 * This method erases the queue associated to an agent identificator. It
	 * check first if the agent is already registered (not yet now:after it
	 * checks if no messages are waiting to be received from the agent and) then
	 * it erases the vector queue from the receptionHashtable.
	 * 
	 * @param agentId
	 *            The identificator of the agent that dosen't want to receive
	 *            message anymore.
	 */
	public synchronized void removeAgentReception(Identificator agentId) {
		if (receptionHashtable.containsKey(agentId.toString())) {
			receptionHashtable.remove(agentId.toString());
			/*
			 * if (((VectorQueue) receptionHashtable.get(agentId.toString()))
			 * .isEmpty()) { //I'm going to erase the message queue for the
			 * agent receptionHashtable.remove(agentId.toString()); } else {
			 * //Ok now i remove the queue
			 * receptionHashtable.remove(agentId.toString());
			 * HermesLogger.log(HermesLogger.INFO, "Agent:" + agentId + " the
			 * message queue of the agent is not empty"); }
			 */
		} else {
			HermesLogger.log(HermesLogger.DEBUG, "Agent:" + agentId
					+ " not registered...nothing changed!");
		}
	}

	/**
	 * Returns the queue associated to an agent identificator. It check first if
	 * the agent is already registered (not yet now:after it checks if no
	 * messages are waiting to be received from the agent and) then it gets the
	 * vectoer queue from the receptionHashtable.
	 * 
	 * @param agentId
	 *            The identificator of the agent.
	 * @return The agent message queue;
	 */
	public synchronized MessageQueue<Message <?>> getAgentMessageQueue(Identificator agentId) {
		MessageQueue<Message <?>>  vq = null;
		if (receptionHashtable.containsKey(agentId.toString())) {
			vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId.toString());
			/*
			 * if (((VectorQueue) receptionHashtable.get(agentId.toString()))
			 * .isEmpty()) { //I'm going to erase the message queue for the
			 * agent receptionHashtable.remove(agentId.toString()); } else {
			 * //Ok now i remove the queue
			 * receptionHashtable.remove(agentId.toString());
			 * HermesLogger.log(HermesLogger.INFO, "Agent:" + agentId + " the
			 * message queue of the agent is not empty"); }
			 */
		} else {
			HermesLogger.log(HermesLogger.DEBUG, "Agent:" + agentId
					+ " not registered...nothing changed!");
		}
		return vq;
	}

	/**
	 * Checks if an agent wants or not receive messages.
	 * 
	 * @param agentId
	 *            The identificator of the agent to check.
	 * 
	 * @return True if the agent is registered false otherwise.
	 */
	public boolean isAgentRegistered(Identificator agentId) {
		return (receptionHashtable.containsKey(agentId.toString()));
	}

	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue the caller will wait a new messages. If the agent is not
	 * registered into the component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageSynch(Identificator agentId) {
		Message<?> tempMsg = null;
		if (receptionHashtable.containsKey(agentId.toString())) {
			MessageQueue<Message <?>>  vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId
					.toString());
			try {
				tempMsg = vq.pull();
			} catch (InterruptedException ie) {
			}
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}

	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue the caller will wait a new messages. If the agent is not
	 * registered into the component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @param senderIds
	 *            The array of identificators of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageSynch(Identificator agentId, Identificator[] senderIds) {
		Message<?> tempMsg = null;
		if (receptionHashtable.containsKey(agentId.toString())) {
		    MessageQueue<Message<?>> vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId
					.toString());
			try {
				tempMsg =  vq.pull(senderIds);
			} catch (InterruptedException ie) {
			}
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue the caller will wait a new messages. If the agent is not
	 * registered into the component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @param senderNames
	 *            The array of name of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageSynch(Identificator agentId, String[] senderNames) {
		Message<?> tempMsg = null;
		if (receptionHashtable.containsKey(agentId.toString())) {
		    MessageQueue<Message<?>> vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId
					.toString());
			try {
				tempMsg = vq.pull(senderNames);
			} catch (InterruptedException ie) {
			}
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}

	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue it returns null. If the agent is not registered into the
	 * component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageAsynch(Identificator agentId) {
		Message<?> tempMsg;
		if (receptionHashtable.containsKey(agentId.toString())) {
		    MessageQueue<Message<?>> vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId
					.toString());

			tempMsg = vq.poll();
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}

	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue it returns null. If the agent is not registered into the
	 * component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * @param senderIds
	 *            The array of identificators of sender agents that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageAsynch(Identificator agentId,
			Identificator[] senderIds) {
		Message<?> tempMsg;
		if (receptionHashtable.containsKey(agentId.toString())) {
		    MessageQueue<Message<?>> vq = (MessageQueue<Message <?>>)receptionHashtable.get(agentId
					.toString());

			tempMsg = vq.poll(senderIds);
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue it returns null. If the agent is not registered into the
	 * component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * @param senderNames
	 *            The array of names of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageAsynch(Identificator agentId,
			String[] senderNames) {
		Message<?> tempMsg;
		if (receptionHashtable.containsKey(agentId.toString())) {
			MessageQueue<Message<?>> vq = (MessageQueue<Message <?>>) receptionHashtable.get(agentId
					.toString());

			tempMsg =  vq.poll(senderNames);
		} else {
			HermesLogger
					.log(
							HermesLogger.DEBUG,
							"Agent:"
									+ agentId
									+ " is not registered in the Communication Manager; no messages queue");

			tempMsg = null;
		}
		return tempMsg;
	}
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue it returns null. If the agent is not registered into the
	 * component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * @param senderName
	 *            The name of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageAsynch(Identificator agentId,
			String senderName) {
	    Message<?> tmpMsg = getMessageAsynch(agentId,new String[]{senderName});
	    return tmpMsg;
	}
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue the caller will wait a new messages. If the agent is not
	 * registered into the component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @param senderName
	 *            The name of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageSynch(Identificator agentId, String senderName) {
	    Message<?> tmpMsg = getMessageSynch(agentId,new String[]{senderName});
	    return tmpMsg;
	}
	
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue it returns null. If the agent is not registered into the
	 * component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * @param senderId
	 *            The identificator of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageAsynch(Identificator agentId,
			Identificator senderId) {
	    Message<?> tmpMsg = getMessageAsynch(agentId,new Identificator[]{senderId});
	    return tmpMsg;
	}
	
	/**
	 * Returns the first message waiting into the blocking queue associated with
	 * the identificator of the agent registered. If no messages are present
	 * into the queue the caller will wait a new messages. If the agent is not
	 * registered into the component this method returns null.
	 * 
	 * @param agentId
	 *            The identificator of the agent that wants to receive a
	 *            message.
	 * 
	 * @param senderId
	 *            The name of the sender agent that it has to search
	 *            into the messages queue.
	 * 
	 * @return The message waiting or null if the agent is not registered.
	 */
	public Message<?> getMessageSynch(Identificator agentId, Identificator senderId) {
	    Message<?> tmpMsg = getMessageSynch(agentId,new Identificator[]{senderId});
	    return tmpMsg;
	}

}